/***********************************************************************************
** Company      :
** File Name    :key.c
** Author       :lusd
** Last Modified:2021-01-25 lusd
** Version      :v1.0
** Description  :此文件包含了按键驱动相关的结构体变量定义，以及接口函数实现。
************************************************************************************/

#include "key.h"
#include "stdio.h"
#include "main.h"

key_scan_t key = {0};

key_info_t keys_info[HW_KEYS_NUM] = {
   {KEY1_INPUT_VALUE,   KEY1_SHORT,   KEY1_LONG,   KEY_NONE,  KEY1_DOUBLE,   200},
   {KEY2_INPUT_VALUE,   KEY2_SHORT,   KEY2_LONG,   KEY_NONE,  KEY2_DOUBLE,   200},
   {KEY3_INPUT_VALUE,   KEY3_SHORT,   KEY3_LONG,   KEY_NONE,  KEY3_DOUBLE,   200},
   {KEY4_INPUT_VALUE,   KEY4_SHORT,   KEY4_LONG,   KEY_NONE,  KEY4_DOUBLE,   200},
   {KEY5_INPUT_VALUE,   KEY5_SHORT,   KEY5_LONG,   KEY_NONE,  KEY5_DOUBLE,   200},
   {KEY6_INPUT_VALUE,   KEY6_SHORT,   KEY6_LONG,   KEY_NONE,  KEY6_DOUBLE,   200},
   {KEY7_INPUT_VALUE,   KEY7_SHORT,   KEY7_LONG,   KEY_NONE,  KEY7_DOUBLE,   200},
   {KEY8_INPUT_VALUE,   KEY8_SHORT,   KEY8_LONG,   KEY_NONE,  KEY8_DOUBLE,   200},
   {KEY9_INPUT_VALUE,   KEY9_SHORT,   KEY9_LONG,   KEY_NONE,  KEY9_DOUBLE,   200},
   {KEY10_INPUT_VALUE,  KEY10_SHORT,  KEY10_LONG,  KEY_NONE,  KEY10_DOUBLE,  200},
   {KEY11_INPUT_VALUE,  KEY11_SHORT,  KEY11_LONG,  KEY_NONE,  KEY11_DOUBLE,  200},
   {KEY12_INPUT_VALUE,  KEY12_SHORT,  KEY12_LONG,  KEY_NONE,  KEY12_DOUBLE,  200},
   {KEY13_INPUT_VALUE,  KEY13_SHORT,  KEY13_LONG,  KEY_NONE,  KEY13_DOUBLE,  200},
   {KEY14_INPUT_VALUE,  KEY14_SHORT,  KEY14_LONG,  KEY_NONE,  KEY14_DOUBLE,  200},
   {KEY15_INPUT_VALUE,  KEY15_SHORT,  KEY15_LONG,  KEY_NONE,  KEY15_DOUBLE,  200},
   {KEY16_INPUT_VALUE,  KEY16_SHORT,  KEY16_LONG,  KEY_NONE,  KEY16_DOUBLE,  200},
};

extern void KEY_GPIO_input_pullup(uint32_t Pin);
extern void KEY_GPIO_output_od_Low(uint32_t Pin);

static uint16_t get_key_input(void)
{
    uint16_t key_val = NOKEY_INPUT_VALUE;
    uint16_t input;
    uint16_t row = 0; // 行
    uint16_t column = 0; // 列

    KEY_GPIO_input_pullup(KEY_R1_Pin|KEY_R2_Pin|KEY_R3_Pin|KEY_R4_Pin); // 行设为输入上拉
    KEY_GPIO_output_od_Low(KEY_L1_Pin|KEY_L2_Pin|KEY_L3_Pin|KEY_L4_Pin); // 列设为开漏输出Low

    input = GPIOA->IDR; // 读取原始输入
    if(!(input & KEY_R1_Pin)) { // PA4输入低电平有效
        row |= KEY_R1_Pin;
    }
    if(!(input & KEY_R2_Pin)) { // PA5输入低电平有效
        row |= KEY_R2_Pin;
    }
    if(!(input & KEY_R3_Pin)) { // PA6输入低电平有效
        row |= KEY_R3_Pin;
    }
    if(!(input & KEY_R4_Pin)) { // PA7输入低电平有效
        row |= KEY_R4_Pin;
    }
    
    KEY_GPIO_input_pullup(KEY_L1_Pin|KEY_L2_Pin|KEY_L3_Pin|KEY_L4_Pin); // 列设为输入上拉
    KEY_GPIO_output_od_Low(KEY_R1_Pin|KEY_R2_Pin|KEY_R3_Pin|KEY_R4_Pin); // 行设为开漏输出Low

    input = GPIOA->IDR; // 读取原始输入
    if(!(input & KEY_L1_Pin)) { // PA0输入低电平有效
        column |= KEY_L1_Pin;
    }
    if(!(input & KEY_L2_Pin)) { // PA1输入低电平有效
        column |= KEY_L2_Pin;
    }
    if(!(input & KEY_L3_Pin)) { // PA2输入低电平有效
        column |= KEY_L3_Pin;
    }
    if(!(input & KEY_L4_Pin)) { // PA3输入低电平有效
        column |= KEY_L4_Pin;
    }

    KEY_GPIO_input_pullup(KEY_R1_Pin|KEY_R2_Pin|KEY_R3_Pin|KEY_R4_Pin); // 行设为输入上拉
    
    key_val = row | column;

    return key_val; 
}

/**
 * @brief: 第一次检测到按键按下,获取输入按键在key_info_t信息表的ID
 *          该函数仅在key.pressed=0时被调用.
 * @author: lusd
 * @param [in] new_input,当前的按键输入
 * @return 短按有效时,返回当前按键ID(0到HW_KEYS_NUM-1);
			否则返回HW_KEYS_NUM.
 */
static uint8_t get_key_id(uint16_t new_input)
{
    uint8_t key_id = HW_KEYS_NUM;
	uint8_t i = 0;
	
    if (0 == key.cnt){	// 第一次判断
        key.last_input = new_input;
    } else if (key.last_input != new_input) { 
        // 为了支持组合键:非第一次判断时,如果输入发生变化,则清零并对last_input重新赋值.
        key.last_input = new_input;
        key.cnt = 0;
    }
    if (key.cnt < SHORT_KEY_DELAY) { // 按键按下计时变量++	
        key.cnt++;
    }
    if (SHORT_KEY_DELAY == key.cnt){
        key.pressed = 1;	// 短按有效标志位
        for (i=0; i<HW_KEYS_NUM; i++) { // 通过对比key_info_t信息表,获取当前按键ID
            if (keys_info[i].hw_input_val == new_input) {
                key_id = i;
				// XXX 调试用: 按键有效时输出,用于观察短按,长按,超长按的时间长度.
                // printf("KEY Press.\r\n"); // 屏蔽打印信息，避免移植时未忘记屏蔽,导致程序卡死在此处
                break;
            }
        }
    }

    return key_id;
}

/**
 * @brief: 处于按下状态的处理：检测长按键,连续按是否有效
 * @author: lusd
 * @param [in] new_input,当前的按键输入
 * @return 按键有效则返回相应键值,无效则返回KEY_NONE
 */
static uint8_t key_pressed_handle(uint16_t new_input)
{
    uint8_t res = KEY_NONE;
    if (key.last_input != new_input) {  
        // 为了支持组合键:非第一次判断时,如果输入发生变化,则清零并对last_input重新赋值.
        key.last_input = new_input;
        key.pressed = 0;
        key.cnt = 0;
    } else if (key.id < HW_KEYS_NUM) {
        key.cnt++;
        if (new_input == key.last_input) {
            if (SHORT_KEY_DELAY == key.cnt) {
                key.pressed = 1; // 标记按键按下生效
            } else if (keys_info[key.id].long_cnt == key.cnt) { // 长按达到2秒钟
                res = keys_info[key.id].long_key_val; // 长按键值
            } else if ((keys_info[key.id].long_cnt + CNTINUS_KEY_DELAY) == key.cnt) { 
                // 长按2秒之后,每持续0.2秒返回一次键值
                key.cnt = keys_info[key.id].long_cnt;
                res = keys_info[key.id].cntinus_key_val; // 一直按,连续触发的键值
            }
        }
    }
    return res;
}

/**
 * @brief: 按键释放时,判定是否返回短按值或双击键值
 * @author: lusd
 * @param none.
 * @return 可能返回短按值(对不支持双击的按键),
 *         或双击键值(对支持双击的按键),
 *         无效则返回KEY_NONE
 */
static uint8_t key_release_handle(void)
{
    uint8_t res = KEY_NONE;

    if (key.pressed) {
        if (key.id < HW_KEYS_NUM) { 
            if (key.cnt < keys_info[key.id].long_cnt) { // 按下的时长，小于长按判定时间
                res = keys_info[key.id].short_key_val; // 短按键值.
                // 如果当前按键支持双击
                if (KEY_NONE != keys_info[key.id].double_key_val) { 
                    if (key.wait_double[key.id]) {
                        key.wait_double[key.id] = 0; // 清除等待双击标志
                        key.double_timeout[key.id] = 0;
                        res = keys_info[key.id].double_key_val; // 双击键值
                    } else {
                        key.wait_double[key.id] = 1; // 设置等待双击标志
                        key.double_timeout[key.id] = DOUBLE_KEY_DELAY; // 设置超时时间
                        key.wait_double_flag = 1;
                        res = KEY_NONE;
                    }
                }
            }
        }
    }
    key.cnt = 0;
    key.pressed = 0;
    key.last_input = NOKEY_INPUT_VALUE;

    return res;
}

/**
 * @brief: 判定等待双击是否超时,超时则返回短按值
 * @author: lusd
 * @param none.
 * @return 等待双击超时则返回短按键值,无效则返回KEY_NONE
 */
static uint8_t key_wait_double_timeout_handle(void)
{
    uint8_t res = KEY_NONE;
    uint8_t i;

    key.wait_double_flag = 0;
    for (i = 0; i < HW_KEYS_NUM; i++) {
        if (key.double_timeout[i]) {	// 如果按键正在等待双击
            key.double_timeout[i]--;
            key.wait_double_flag = 1;
            if (0 == key.double_timeout[i]) { // 减到0的时刻,表示等待超时了
                key.wait_double[i] = 0; // 清除等待双击标志
                return (keys_info[i].short_key_val); // 返回该键的短按值
            }
        }
    }
    return res;
}

// 按键扫描函数,需每10ms调用一次
uint8_t key_scan(void)
{
    uint8_t res = KEY_NONE;
    uint16_t key_input;

	key.scan_flag = 0;
    key_input = get_key_input();
    if (NOKEY_INPUT_VALUE != key_input) {
        if (0 == key.pressed) {
            // 上次状态为"非按下",则获取当前按键ID
            key.id = get_key_id(key_input);
		} else {
            res = key_pressed_handle(key_input);
        }
    } else {
        res = key_release_handle();
    }
    if (KEY_NONE == res) { // 前面的处理没有产生有效键值,再处理双击超时
        if (key.wait_double_flag) {
            res = key_wait_double_timeout_handle();
        } 
    }

    return res;
}

#if 0 // 在工程中已经实现下面两个函数.这里仅用作使用说明

// 假设这是一个10ms的定时中断服务函数
void timer_isr(void)
{
    key.scan_flag = 1;
    // 清除定时器中断标志位等
    // ...
}

int main(void)
{
    uint8_t key_val;

    // 硬件初始化
    // ...
    
    while (1) {
        if (key.scan_flag) {
            key_val = key_scan();
            switch (key_val) {
                // 按键响应处理
            }
        }
        // 其他功能处理
        // ...
    }
    return 0;
}
#endif
